/*
 * Copyright 2009 Johnson Consulting Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * 
 */
package com.johnsoncs.gwt.application.client.validation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.johnsoncs.gwt.application.client.model.QueryPath;
import com.johnsoncs.gwt.widgets.client.util.Callback;

/**
 * @author wilj
 *
 */
public class Validator {
	private class ConstraintMapping {
		private final QueryPath path;
		private final List<Constraint> constraints = new ArrayList<Constraint>();

		public ConstraintMapping(final QueryPath path) {
			this.path = path;
		}

		public void addConstraint(final Constraint constraint) {
			constraints.add(constraint);
		}

		public List<Constraint> getConstraints() {
			return this.constraints;
		}

		public QueryPath getQueryPath() {
			return this.path;
		}
	}

	private class ValidatorImpl implements Callback<ValidationState> {
		Iterator<ConstraintMapping> mappingsItr;
		ConstraintMapping currentMapping;
		Iterator<Constraint> constraintsItr;
		Constraint currentConstraint;
		List<ValidationValue> values;
		Iterator<ValidationValue> valuesItr;
		final List<ValidationState> results = new ArrayList<ValidationState>();

		/* (non-Javadoc)
		 * @see com.johnsoncs.gwt.widgets.client.util.Callback#exec(java.lang.Object)
		 */
		@Override
		public void exec(final ValidationState value) {
			if (value != null) {
				results.add(value);
			}
			next();
		}

		public void next() {
			if (mappingsItr == null) {
				// just started
				mappingsItr = constraints.values().iterator();
				currentMapping = mappingsItr.next();
				constraintsItr = currentMapping.getConstraints().iterator();
				currentConstraint = constraintsItr.next();
				values = context.getValues(currentMapping.getQueryPath());
				valuesItr = values.iterator();
			}
			if (valuesItr.hasNext()) {
				currentConstraint.applyTo(valuesItr.next(), context, this);
			} else if (constraintsItr.hasNext()) {
				currentConstraint = constraintsItr.next();
				valuesItr = values.iterator();
				next();
			} else if (mappingsItr.hasNext()) {
				currentMapping = mappingsItr.next();
				values = context.getValues(currentMapping.getQueryPath());
				valuesItr = values.iterator();
				constraintsItr = currentMapping.getConstraints().iterator();
				currentConstraint = constraintsItr.next();
				next();
			} else {
				for (final Callback<List<ValidationState>> c : validationCallbacks) {
					c.exec(results);
				}
			}
		}

	}

	private final ValidationContext context;

	private final Map<String, ConstraintMapping> constraints = new HashMap<String, ConstraintMapping>();

	private final Set<Callback<List<ValidationState>>> validationCallbacks = new HashSet<Callback<List<ValidationState>>>();

	public Validator(final ValidationContext context) {
		this.context = context;
	}

	public void addConstraint(final String queryPath, final Constraint constraint) {
		ConstraintMapping c = constraints.get(queryPath);
		if (c == null) {
			final QueryPath path = QueryPath.parse(queryPath);
			c = new ConstraintMapping(path);
			constraints.put(queryPath, c);
		}
		c.addConstraint(constraint);
	}

	public void addValidationCallback(final Callback<List<ValidationState>> callback) {
		validationCallbacks.add(callback);
	}

	public void validate() {
		new ValidatorImpl().next();
	}
}
